/*
 * Created on Oct 2, 2007
 */
package com.mgmaps.net;

import java.io.*;
import java.util.*;
import javax.microedition.io.*;
import com.mgmaps.tools.StringTools;

/**
 * Wrapper for an HTTP form - only POST method supported for now.
 *
 * @author CristiS
 * @version 1.0
 */
public class HTTPForm implements Downloadable {
  // form encoding
  public static final int ENCTYPE_CUSTOM = -1;
  public static final int ENCTYPE_URLENCODE = 0;
  public static final int ENCTYPE_MULTIPART = 1;
  public static final int ENCTYPE_PLAIN = 2;
  private static final String[] ENCTYPE_STRING = {
    "application/x-www-form-urlencoded",
    "multipart/form-data",
    "text/plain"
  };
  private int encType;
  private String encTypeString;

  // multipart boundary
  private String boundary;
  
  // method
  private String method;
  
  // post URL
  private String url;
  
  // form fields
  public Vector fields;
  
  // whether to process cookies
  public boolean processCookies;
  
  /**
   * A field in a form.
   *
   * @author CristiS
   * @version 1.0
   */
  public static class Field {
    String key;
    Object data;
    String contentType;
    String filename;
    
    /**
     * Constructor for Field.
     *
     * @param key field name
     * @param value field data
     */
    public Field(String key, Object value) {
      this.key = key;
      this.data = value;
    }
  }
  
  /**
   * Default constructor for HTTPForm.
   *
   * @param url URL to get
   */
  public HTTPForm(String url) {
    this(url, HttpConnection.GET, ENCTYPE_PLAIN);
  }
  
  /**
   * Constructor for HTTPForm.
   *
   * @param url URL to POST
   * @param encType encoding type
   */
  public HTTPForm(String url, String method, int encType) {
    this.url = url;
    this.method = method;
    this.encType = encType;
    fields = new Vector();
  }
  
  /**
   * Constructor for HTTPForm.
   *
   * @param url URL to POST
   * @param encTypeString encoding type
   */
  public HTTPForm(String url, String method, String encTypeString) {
    this.url = url;
    this.method = method;
    this.encType = ENCTYPE_CUSTOM;
    this.encTypeString = encTypeString;
    fields = new Vector();
  }
  
  /**
   * Get URL for this downloadable object.
   * @return url
   * @see com.mgmaps.net.Downloadable#getURL()
   */
  public String getURL() {
    return url;
  }
  
  /**
   * Add a string field to the list of fields.
   * @param key field name
   * @param value field contents (encoding UTF-8)
   */
  public void addStringField(String key, String value) {
    fields.addElement(new Field(key, value));
  }
  
  /**
   * Add a data field.
   * 
   * @param key field name
   * @param value field contents
   * @param contentType content type
   * @param filename file name
   */
  public void addDataField(String key, byte[] value, String contentType, String filename) {
    // set encoding type
    encType = ENCTYPE_MULTIPART;
    Field f = new Field(key, value);
    f.contentType = contentType;
    f.filename = filename;
    fields.addElement(f);
  }
  
  /**
   * Get additional headers.
   * 
   * @return additional headers, nothing by default
   */
  public Hashtable getAdditionalHeaders() {
    return new Hashtable();
  }
  
  /**
   * Get the content type for this form.
   * 
   * @return the content type
   */
  public String getContentType() {
    if (encType == ENCTYPE_CUSTOM)
      return encTypeString;
    
    String result = ENCTYPE_STRING[encType];
    if (encType == ENCTYPE_MULTIPART) {
      // generate boundary
      if (boundary == null)
        boundary = StringTools.generateMimeBoundary();
      result += "; boundary=" + boundary;
    }
    return result;
  }
  
  /**
   * Get the method.
   * 
   * @return the method
   */
  public String getMethod() {
    return method;
  }
  
  /**
   * Build the request content for this form.
   * 
   * @return the content as a byte array
   */
  public byte[] getRequest() throws IOException {
    // no fields needed
    int sz = fields.size();
    if (sz == 0)
      return new byte[0];
    
    // generate boundary
    if (boundary == null)
      boundary = StringTools.generateMimeBoundary();

    // multipart
    if (encType == ENCTYPE_MULTIPART) {
      final String crlf = "\r\n";
      ByteArrayOutputStream bos = new ByteArrayOutputStream();
      for (int i=0; i<sz; i++) {
        Field field = (Field) fields.elementAt(i);
        boolean isString = (field.data instanceof String);
        // boundary
        StringBuffer sb = new StringBuffer("--");
        sb.append(boundary);
        sb.append(crlf);
        // content disposition
        sb.append("Content-Disposition: form-data; name=\"");
        sb.append(field.key);
        sb.append('\"');
        if (!isString) {
          sb.append("; filename=\"");
          sb.append(field.filename);
          sb.append('\"');
        }
        sb.append(crlf);
        // content type
        if (!isString) {
          sb.append("Content-Type: ");
          sb.append(field.contentType);
          sb.append(crlf);
        }
        // end of part header
        sb.append(crlf);
        bos.write(sb.toString().getBytes());
        // data (encapsulation)
        if (isString)
          bos.write(((String) field.data).getBytes());
        else
          bos.write((byte[]) field.data);
        bos.write(crlf.getBytes());
      }
      // end boundary
      bos.write("--".getBytes());
      bos.write(boundary.getBytes());
      bos.write("--".getBytes());
      return bos.toByteArray();
    }
    
    // urlencoded
    StringBuffer result = new StringBuffer();
    for (int i=0; i<sz; i++) {
      Field field = (Field) fields.elementAt(i);
      if (i > 0)
        result.append('&');
      result.append(field.key);
      result.append('=');
      if (field.data instanceof String)
        result.append(StringTools.urlEncode((String) field.data));
      // files are only accepted with multipart
    }
    // encoding does not matter, we have already urlencoded everything
    return result.toString().getBytes();
  }
  
  /**
   * Equals method to avoid enqueuing several times.
   * @param other other object to compare to
   * @return true if the URLs are equal.
   * @see java.lang.Object#equals(java.lang.Object)
   */
  public boolean equals(Object other) {
    if (!(other instanceof HTTPForm))
      return false;
    HTTPForm otherForm = (HTTPForm) other;
    return ((otherForm.url != null) && ((HTTPForm) other).url.equals(url));
  }

  /**
   * Called when the download is complete.
   * 
   * @param data submission complete data
   * @see com.mgmaps.net.Downloadable#notifyDownloaded(byte[][])
   */
  public void notifyDownloaded(byte[][] data) {
    Downloader.defaultHandler.notifyDownloaded(url, data);
  }

  /**
   * Called when the download returns an error.
   * 
   * @see com.mgmaps.net.Downloadable#notifyError()
   */
  public void notifyError(String message) {
    Downloader.defaultHandler.notifyError(url, message);
  }
}
